package com.hontech.bread.device.mdb

import com.hontech.bread.R
import com.hontech.bread.device.Conn
import com.hontech.bread.utils.await
import com.hontech.bread.utils.log
import com.hontech.bread.utils.postEvent
import com.hontech.bread.utils.runOnUiCatchAny
import com.hontech.bread.utils.stringById
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.delay
import serial.codec.FlexArray
import serial.codec.Uint16
import serial.codec.Uint32
import serial.codec.Uint8

class PosJustReset

class PosSessionBegin(
    val availableValue: Int
)

class PosVendApproved(
    val value: Int
)

class PosSessionCancel

class PosVendDenied

class PosSessionEnd

class PosModelInfo(
    val manufacturerCode: String,
    val serialNumber: String,
    val model: String,
    val softVersion: String
) {
    companion object {
        private var instance: PosModelInfo? = null

        fun get(): PosModelInfo {
            return instance ?: throw IllegalStateException(
                "POS${stringById(R.string.t1733460848_181)} ${
                    stringById(
                        R.string.t1733460848_182
                    )
                } ${stringById(R.string.t1733460848_183)}"
            )
        }
    }

    init {
        instance = this
    }
}

object Pos {

    private const val RESET = 0x10
    private const val SETUP = 0x11
    private const val POLL = 0x12
    private const val VEND = 0x13
    private const val READER = 0x14
    private const val REVALUE = 0x15
    private const val EXT = 0x17

    private const val PAY_SUCCESS = 0
    private const val PAY_DENIED = 1
    private const val PAY_CANCEL = 2

    private var sessionBeginPromise: CompletableDeferred<Unit>? = null
    private var vendPromise: CompletableDeferred<Int>? = null
    private var sessionEndPromise: CompletableDeferred<Unit>? = null

    private var resetFlag = false

    suspend fun onceReset() {
        if (!resetFlag) {
            reset(true)
        } else {
            enable()
        }
    }

    suspend fun reset(enable: Boolean = true) {
        log("POS RESET")
        Conn.mdbReq(RESET).assertAck()
        resetInit(enable)
        resetFlag = true
    }

    suspend fun vendRequest(value: Int, timeout: Long) {
        val vend = CompletableDeferred<Int>()
        vendPromise = vend

        Conn.mdbReq(VEND, Uint8(0), Uint16(value), Uint16(0xFFFF)).assertAck()

        val r = try {
            vend.await(timeout, "${stringById(R.string.t1733460848_184)}")
        } catch (e: Exception) {
            vendCancel()
            throw e
        }

        if (r == PAY_SUCCESS) {
            return
        } else if (r == PAY_DENIED) {
            throw IllegalStateException(stringById(R.string.t1733460848_185))
        } else if (r == PAY_CANCEL) {
            throw IllegalStateException(stringById(R.string.t1733460848_186))
        } else {
            throw IllegalStateException("${stringById(R.string.t1733460848_187)}:$r")
        }
    }

    suspend fun vendTest(value: Int, timeout: Long = 10 * 1000) {

        val session = CompletableDeferred<Unit>()
        sessionBeginPromise = session

        session.await(
            timeout,
            "${stringById(R.string.t1733460848_188)}POS${stringById(R.string.t1733460848_189)}"
        )

        vendRequest(value, timeout)

        delay(1000)

        vendSuccess()
    }

    suspend fun vendCancel() {
        Conn.mdbReq(VEND, Uint8(1)).assertAck()
    }

    suspend fun vendSuccess() {
        Conn.mdbReq(VEND, Uint8(2), Uint16(0xFFFF)).assertAck()
        vendComplete()
    }

    suspend fun vendFail() {
        Conn.mdbReq(VEND, Uint8(3)).assertAck()
        while (true) {
            val frame = Conn.mdbReq(POLL)
            if (frame.isAck()) {
                break
            }
            delay(500)
        }
        vendComplete()
    }

    private suspend fun vendComplete() {
        val end = CompletableDeferred<Unit>()
        sessionEndPromise = end
        Conn.mdbReq(VEND, Uint8(4)).assertAck()
        end.await(1000, "pos session end timeout")
    }

    private suspend fun resetInit(enable: Boolean) {
        log("POS SETUP")
        setup()
        log("POS MAX MIN PRICE")
        maxMinPrice()
        log("POS MODEL INFO")
        delay(800)
        modelInfo()
        log("POS ENABLE")
        Conn.mdbReq(EXT, Uint8(4), Uint32(1 shl 5)).assertAck()
        if (enable) {
            enable()
        } else {
            disable()
        }
    }

    private suspend fun modelInfo(): PosModelInfo {
        val z2_4 = FlexArray(3)
        val z5_16 = FlexArray(12)
        val z17_28 = FlexArray(12)
        val z29_30 = FlexArray(2)
//        val z31_34 = Uint32()
        val f = Conn.mdbReq(
            EXT, Uint8(0),
            FlexArray(3).set("HON"),
            FlexArray(12).set("0123456789AB"),
            FlexArray(12).set("0987654321BA"),
            Uint16(0x1234)
        )
        f.parse(z2_4, z5_16, z17_28, z29_30)
//        log("res ${f}")
//        log("pos features:${z31_34.value.hex32()}")
        return PosModelInfo(
            z2_4.toString(),
            z5_16.toString(),
            z17_28.toString(),
            z29_30.toBCD()
        )
    }

    private suspend fun maxMinPrice() {
        Conn.mdbReq(SETUP, Uint8(1), Uint16(0xFFFF), Uint16(0)).assertAck()
    }

    suspend fun setup(): PosSetupInfo {
        val z2 = Uint8()
        val z3_4 = Uint16()
        val z5 = Uint8()
        val z6 = Uint8()
        val z7 = Uint8()
        val z8 = Uint8()
        Conn.mdbReq(SETUP, Uint8(0), Uint8(1), Uint8(0), Uint8(0), Uint8(1))
            .parse(1, z2, z3_4, z5, z6, z7, z8)
        return PosSetupInfo(
            z2.value,
            z3_4.value,
            z5.value,
            z6.value,
            z7.value,
            z8.value
        )
    }

    suspend fun enable() {
        Conn.mdbReq(READER, Uint8(1)).assertAck()
    }

    suspend fun disable() {
        Conn.mdbReq(READER, Uint8(0)).assertAck()
    }

    fun onPollNotify(frame: MdbFrame) {
        val head = frame.dataGet(0)
        when (head) {

            0 -> {
                log("pos reset")
            }

            3 -> {
                val z2_3 = Uint16()
                frame.parse(1, z2_3)
                val info = PosSessionBegin(z2_3.value)
                postEvent(info)

                sessionBeginPromise?.complete(Unit)
                sessionBeginPromise = null

                log("pos session begin")
            }

            4 -> {
                val c = PosSessionCancel()
                postEvent(c)
                runOnUiCatchAny { vendComplete() }

                vendPromise?.complete(PAY_CANCEL)
                vendPromise = null

                log("pos session cancel")
            }

            5 -> {
                val z2_3 = Uint16()
                frame.parse(z2_3)
                val info = PosVendApproved(z2_3.value)
                postEvent(info)

                vendPromise?.complete(PAY_SUCCESS)
                vendPromise = null

                log("pos session approved")
            }

            6 -> {
                val c = PosVendDenied()
                postEvent(c)

                runOnUiCatchAny { vendComplete() }

                vendPromise?.complete(PAY_DENIED)
                vendPromise = null

                log("pos session deined")
            }

            7 -> {
                val c = PosSessionEnd()
                sessionEndPromise?.complete(Unit)
                sessionBeginPromise = null

                postEvent(c)
                log("pos session end")
            }


        }
    }
}